/* 
* sapdp-keys.c
*
* Copyright (c) 2010, Zachary N J Peterson <znpeters@nps.edu>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the Naval Postgraduate School nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY ZACHARY N J PETERSON ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ZACHARY N J PETERSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "sapdp.h"


static void destroy_sapdp_generator(SAPDP_generator *g){

	if(!g) return;
	
	BN_clear_free(g);
}

static DPDP_generator *pick_dpdp_generator(BIGNUM *n){
	 
	DPDP_generator *g = NULL; /* generator */

  	if( ((g = BN_new()) == NULL)) goto cleanup;

	if(!BN_rand_range(g, n)) goto cleanup;

	return g;
	
cleanup:
	if(g) BN_clear_free(g);	
	return NULL;
}

DPDP_key *dpdp_generate_key(){

	DPDP_key *key;
	if( (key = malloc(sizeof(DPDP_key))) == NULL) return NULL;
	memset(key, 0, sizeof(DPDP_key));	
	
	if( ((key->rsa = RSA_generate_key(RSA_KEY_SIZE, RSA_E, NULL, NULL)) == NULL)) goto cleanup;
	/* Pick a PDP generator */
	if( ((key->g = pick_dpdp_generator(key->rsa->n)) == NULL)) goto cleanup;
	
	return key;

cleanup:
	if(key) sapdp_destroy_key(key);
	return NULL;
}


/*
 * pick_pdp_generator: Finds a generator of the quadratic residues subgroup of Z*N.
 * Takes an RSA modulus N and returns a generator or NULL on failure..
 */
SAPDP_generator *pick_sapdp_generator(BIGNUM *n){

 	SAPDP_generator *g = NULL; /* generator */
	BIGNUM *a = NULL; /* random value */
	BIGNUM *r0 = NULL; /* temp value */
	BIGNUM *r1 = NULL; /* temp value */
  	BN_CTX *ctx = NULL;
  	int found_g = 0;

	if(!n) return NULL;

  	if( ((g = BN_new()) == NULL)) goto cleanup;
	if( ((a = BN_new()) == NULL)) goto cleanup;
	if( ((r0 = BN_new()) == NULL)) goto cleanup;
	if( ((r1 = BN_new()) == NULL)) goto cleanup;
	if( ((ctx = BN_CTX_new()) == NULL)) goto cleanup;
	
 	while(!found_g){

    	/* Pick a random a < N */
		if(!BN_rand_range(a, n)) goto cleanup;

 		/* Check to see if a is relatively prime to N */
    	/* gcd(a, N) = 1 */
		if(!BN_gcd(r0, a, n, ctx)) goto cleanup;
    	if(!BN_is_one(r0))
      		continue;

    	/* Check to see if a-1 is relatively prime to N */
    	/* gcd(a-1, N) = 1 */
		if(!BN_sub(r0, a, BN_value_one())) goto cleanup;
		if(!BN_gcd(r1, r0, n, ctx)) goto cleanup;
    	if(!BN_is_one(r1))
			continue;

    	/* Check to see if a+1 is relatively prime to N */
    	/* gcd(a+1, N) = 1 */
		if(!BN_add(r0, a, BN_value_one())) goto cleanup;
		if(!BN_gcd(r1, r0, n, ctx)) goto cleanup;
    	if(!BN_is_one(r1))
			continue;

    	found_g = 1;
	}

	/* Square a to get a generator of the quadratic residues */
	if(!BN_sqr(g, a, ctx)){ found_g = 0; goto cleanup; }

cleanup:
	if(ctx)	BN_CTX_free(ctx);
	if(a) BN_clear_free(a);
	if(r0) BN_clear_free(r0);
	if(r1) BN_clear_free(r1);

	if(found_g)
		return g;
	else{
		if(g) BN_clear_free(g);
		return NULL;
	}	
}

void sapdp_destroy_key(SAPDP_key *key){
	
	if(!key) return;
	if(key->rsa) RSA_free(key->rsa);
#ifdef USE_FDPRF
	if(key->k_prf)  sfree(key->k_prf, PRF_KEY_SIZE);
#endif
	if(key->g) destroy_sapdp_generator(key->g);
	if(key) sfree(key, sizeof(SAPDP_key));
}

SAPDP_key *sapdp_generate_key(){
	
	SAPDP_key *key = NULL;
	
	if( (key = malloc(sizeof(SAPDP_key))) == NULL) return NULL;
	memset(key, 0, sizeof(SAPDP_key));	
	
	if( ((key->rsa = RSA_generate_key(RSA_KEY_SIZE, RSA_E, NULL, NULL)) == NULL)) goto cleanup;
	if( ((key->k_prf = malloc(PRF_KEY_SIZE)) == NULL)) goto cleanup;
	
	/* Generate symmetric keys */
	if(!RAND_bytes(key->k_prf, PRF_KEY_SIZE)) goto cleanup;	
	
	/* Pick a PDP generator */
	if( ((key->g = pick_sapdp_generator(key->rsa->n)) == NULL)) goto cleanup;
	
	return key;

cleanup:
	if(key) sapdp_destroy_key(key);
	return NULL;
}